
package todolist.storage;

import java.io.File;
import java.io.IOException;
import javax.swing.JOptionPane;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import todolist.entity.TaskEntity;
import todolist.logic.task.TaskChangedListener;
import todolist.personalization.ErrorMessages;

/**
 * Writes the necessary changes to file using XMLParser/DOM. Singleton class.
 * @author YouLi
 * @author Ivan
 */
public class Export implements DocumentDeletedListener, TaskChangedListener {

	// handle singleton instance
	
	private static Export _objExport;

	public static Export getInstance() {
		if (_objExport == null)
			_objExport = new Export();
		return _objExport;
	}
	
	// member attributes

	private Document _objDoc;
	private Element _objRootElement;
	
	private static final String EXPORT_FILE = "toDoList.xml";
	
	public Export() {
		try {
			loadDocument();
		} catch (Exception ex) {
			JOptionPane.showMessageDialog(null,
				ErrorMessages.ERR_EXPORT_FAILED_TO_INITIALIZE_EXPORT,
		    	"Export Failed", JOptionPane.WARNING_MESSAGE, null);
		}
	}

	/**
	 * One time initialisation of DOM. Can be used for re-initialisation too.
	 * Task List file does not need to exist.
	 * Root element will be created if it does not already exist.
	 */
	private void loadDocument() throws SAXException, IOException {
		DocumentBuilderFactory objDocFactory = DocumentBuilderFactory.newInstance();
		try {
			DocumentBuilder objDocBuilder = objDocFactory.newDocumentBuilder();
			_objDoc = objDocBuilder.newDocument();
			File objFile = new File(EXPORT_FILE);
			if (objFile.exists())
				_objDoc = objDocBuilder.parse(objFile);
		} catch (ParserConfigurationException ex) {
			JOptionPane.showMessageDialog(null,
				ErrorMessages.ERR_EXPORT_FAILED_TO_PARSE_FILE,
		    	"Export Failed", JOptionPane.WARNING_MESSAGE, null);
		}
		// get root element, creating first if not already exists
		if (_objDoc.getFirstChild() == null) {
			Element rootElement = _objDoc.createElement("TaskEntities");
			_objDoc.appendChild(rootElement);
		}
		_objRootElement = (Element) _objDoc.getFirstChild();
	}

	/**
	 * Creates and returns a new Task element based on a Task Entity.
	 */
	private Element createTaskElement(TaskEntity pentTask){		
		String strTaskId = String.valueOf(pentTask.getTaskId());
		Element elmTask = _objDoc.createElement("taskEntity" + strTaskId);
		appendTaskChildElement(elmTask, "taskID", strTaskId);
		appendTaskChildElement(elmTask, "taskName", pentTask.getTaskName());
		appendTaskChildElement(elmTask, "taskDate", pentTask.getDTString());
		appendTaskChildElement(elmTask, "taskPriority", pentTask.getPriorityString());
		appendTaskChildElement(elmTask, "taskReminderOffSet", String.valueOf(pentTask.getReminderOffset()));
		appendTaskChildElement(elmTask, "taskComment", pentTask.getTaskComments());
		return elmTask;
	}
	
	/**
	 * Appends an element containing a field of Task to parent Task element.
	 */
	private void appendTaskChildElement(Element pelmTask, String pstrChildName, String pstrData) {
		Element elmChild = _objDoc.createElement(pstrChildName);
		elmChild.appendChild(_objDoc.createTextNode(pstrData));
		pelmTask.appendChild(elmChild);
	}
	
	/**
	 * Actual writing to file.
	 */
	private void writeContent() {
		TransformerFactory objTF = TransformerFactory.newInstance();
		try {
			Transformer objTransformer = objTF.newTransformer();
			DOMSource objDOMSource = new DOMSource(_objDoc);
			StreamResult objStreamResult = new StreamResult(new File(EXPORT_FILE));
			objTransformer.transform(objDOMSource, objStreamResult);
		} catch (TransformerException ex) {
			JOptionPane.showMessageDialog(null,
				ErrorMessages.ERR_EXPORT_FAILED,
		    	"Export Failed", JOptionPane.WARNING_MESSAGE, null);
		}
	}
	
	// listener implementation

	/**
	 * Handles writing of Task to file upon being notified of Task added.
	 */
	@Override
	public void taskPostAdd(TaskEntity pentTask) {
		assert pentTask != null;
		Element objTaskElement = createTaskElement(pentTask);
		_objRootElement.appendChild(objTaskElement);
		writeContent();
	}

	/**
	 * Handles writing of Task to file upon being notified of Task edited.
	 */
	@Override
	public void taskPostEdit(TaskEntity pentOldTask, TaskEntity pentNewTask) {
		assert pentNewTask != null;
		NodeList objNodeList = _objRootElement.getElementsByTagName("taskEntity" + pentNewTask.getTaskId());
		assert objNodeList.getLength() == 1;
		Element objTaskElement = createTaskElement(pentNewTask);
		_objRootElement.replaceChild(objTaskElement, objNodeList.item(0));
		writeContent();
	}

	/**
	 * Handles writing of Task to file upon being notified of Task deleted.
	 */
	@Override
	public void taskPostDelete(TaskEntity pentTask) {
		assert pentTask != null;
		NodeList objNode = _objRootElement.getElementsByTagName("taskEntity" + pentTask.getTaskId());
		if (objNode.getLength() > 0)
			_objRootElement.removeChild(objNode.item(0));
		writeContent();
	}

	/**
	 * Handles re-initilization of file/DOM upon being notified that the Task List file has been deleted.
	 */
	@Override
	public void documentDeleted() {
		try {
			loadDocument();
		} catch (Exception ex) {
			JOptionPane.showMessageDialog(null,
				ErrorMessages.ERR_EXPORT_FAILED_TO_INITIALIZE_EXPORT,
		    	"Export Failed", JOptionPane.WARNING_MESSAGE, null);
		}
	}
	
}
